﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace BookSleeve
{
    internal struct RedisProtocolReader
    {
        public RedisProtocolReader(byte[] buffer, int offset, int count, IInputStream input, MemoryStream bodyBuffer)
        {
            this.buffer = buffer;
            this.offset = offset;
            this.count = count;
            this.input = input;
            this.bodyBuffer = bodyBuffer;
        }
        private void BurnCrlf()
        {
            if (ReadByteOrFail() != (byte)'\r' || ReadByteOrFail() != (byte)'\n') throw new InvalidDataException("Expected crlf terminator not found");
        }
        public byte[] ReadBytesToCrlf()
        {
            // check for data inside the buffer first
            int bytes = FindCrlfInBuffer();
            byte[] result;
            if (bytes >= 0)
            {
                result = new byte[bytes];
                Buffer.BlockCopy(buffer, offset, result, 0, bytes);
                // subtract the data; don't forget to include the CRLF
                count -= (bytes + 2);
                offset += (bytes + 2);
            }
            else
            {
                byte[] oversizedBuffer;
                int len = FillBodyBufferToCrlf(out oversizedBuffer);
                result = new byte[len];
                Buffer.BlockCopy(oversizedBuffer, 0, result, 0, len);
            }
            return result;
        }
        int FindCrlfInBuffer()
        {
            int max = offset + count - 1;
            for (int i = offset; i < max; i++)
            {
                if (buffer[i] == (byte)'\r' && buffer[i + 1] == (byte)'\n')
                {
                    int bytes = i - offset;
                    return bytes;
                }
            }
            return -1;
        }
        public string ReadStringToCrlf()
        {
            // check for data inside the buffer first
            int bytes = FindCrlfInBuffer();
            string result;
            if (bytes >= 0)
            {
                result = Encoding.UTF8.GetString(buffer, offset, bytes);
                // subtract the data; don't forget to include the CRLF
                count -= (bytes + 2);
                offset += (bytes + 2);
            }
            else
            {
                // check for data that steps over the buffer
                byte[] oversizedBuffer;
                int len = FillBodyBufferToCrlf(out oversizedBuffer);
                result = Encoding.UTF8.GetString(oversizedBuffer, 0, len);
            }
            return result;
        }
        public byte ReadByteOrFail()
        {
            if (count > 0 || ReadMoreSync())
            {
                count--;
                return buffer[offset++];
            }
            throw new EndOfStreamException();
        }
        private readonly MemoryStream bodyBuffer;
        private int FillBodyBufferToCrlf(out byte[] oversizedBuffer)
        {
            bool haveCr = false;
            bodyBuffer.SetLength(0);
            byte b;
            do
            {
                b = ReadByteOrFail();
                if (haveCr)
                {
                    if (b == (byte)'\n')
                    {// we have our string
                        oversizedBuffer = bodyBuffer.GetBuffer();
                        return (int)bodyBuffer.Length;
                    }
                    else
                    {
                        bodyBuffer.WriteByte((byte)'\r');
                        haveCr = false;
                    }
                }
                if (b == (byte)'\r')
                {
                    haveCr = true;
                }
                else
                {
                    bodyBuffer.WriteByte(b);
                }
            } while (true);
        }
        public bool ReadMoreSync()
        {
            offset = 0;
            int bytesRead = input == null ? 0 : input.Read(buffer, 0, buffer.Length);
            if (bytesRead > 0)
            {
                count = bytesRead;
                return true;
            }
            count = 0;
            return false;
        }
        private int Read(byte[] targetBuffer, int targetOffset, int targetCount)
        {
            if (count > 0 || ReadMoreSync())
            {
                int toCopy = Math.Min(targetCount, count);
                Buffer.BlockCopy(buffer, offset, targetBuffer, targetOffset, toCopy);
                offset += toCopy;
                count -= toCopy;
                return toCopy;
            }
            else
            {
                return 0;
            }
        }
        private static readonly byte[] empty = new byte[0];
        public byte[] ReadBulkBytes()
        {
            int len;
            checked
            {
                len = (int)ReadInt64();
            }
            switch (len)
            {
                case -1: return null;
                case 0: BurnCrlf(); return empty;
            }

            byte[] data = new byte[len];
            int bytesRead, dataOffset = 0;
            while (len > 0 && (bytesRead = Read(data, dataOffset, len)) > 0)
            {
                len -= bytesRead;
                dataOffset += bytesRead;
            }
            if (len > 0) throw new EndOfStreamException("EOF reading bulk-bytes");
            BurnCrlf();
            return data;
        }
        public long ReadInt64()
        {
            byte[] oversizedBuffer;
            int len = FillBodyBufferToCrlf(out oversizedBuffer);
            // crank our own int parser... why not...
            int tmp;
            switch (len)
            {
                case 0:
                    throw new EndOfStreamException("No data parsing integer");
                case 1:
                    if ((tmp = ((int)oversizedBuffer[0] - '0')) >= 0 && tmp <= 9)
                    {
                        return tmp;
                    }
                    break;
            }
            bool isNeg = oversizedBuffer[0] == (byte)'-';
            if (isNeg && len == 2 && (tmp = ((int)oversizedBuffer[1] - '0')) >= 0 && tmp <= 9)
            {
                return -tmp;
            }

            long value = 0;
            for (int i = isNeg ? 1 : 0; i < len; i++)
            {
                if ((tmp = ((int)oversizedBuffer[i] - '0')) >= 0 && tmp <= 9)
                {
                    value = (value * 10) + tmp;
                }
                else
                {
                    throw new FormatException("Unable to parse integer: " + Encoding.UTF8.GetString(oversizedBuffer, 0, len));
                }
            }
            return isNeg ? -value : value;
        }

        public bool HasLocalData { get { return count > 0; } }
        public bool HasRemoteData { get { return input.DataAvailable; } }
        private readonly IInputStream input;
        private readonly byte[] buffer;
        private int offset;
        private int count;
    }
}
